/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkField, ArkMethod, Scene, ArkFile } from "arkanalyzer/lib";
import { MethodParameter } from "arkanalyzer/lib/core/model/builder/ArkMethodBuilder";
import {
  ClassSignature,
  MethodSignature,
} from "arkanalyzer/lib/core/model/ArkSignature";
import Logger, { LOG_MODULE_TYPE } from "arkanalyzer/lib/utils/logger";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import {
  ClassMatcher,
  MatcherCallback,
  MatcherTypes,
  MethodMatcher,
} from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(
  LOG_MODULE_TYPE.HOMECHECK,
  "PreferReadonlyParametertypesCheck"
);
const gMetaData: BaseMetaData = {
  severity: 1,
  ruleDocPath: "docs/prefer-readonly-parameter-types-check.md", // TODO: support url
  description: "Parameter should be a read only type.",
};
type AllowElement =
  | string
  | { from: "file"; name: [string, ...string[]] | string; path?: string }
  | { from: "lib"; name: [string, ...string[]] | string }
  | { from: "package"; name: [string, ...string[]] | string; package: string };

interface ClassifiedItem {
  from: string;
  path?: string;
  names: string[];
}
type Options = {
  /** An array of type specifiers to ignore. */
  allow?: AllowElement[];
  /** Whether to check class parameter properties. */
  checkParameterProperties?: boolean;
  /** Whether to ignore parameters which don't explicitly specify a type. */
  ignoreInferredTypes?: boolean;
  /** Whether to treat all mutable methods as though they are readonly. */
  treatMethodsAsReadonly?: boolean;
};
const defaultOptions: Options = {
  allow: [],
  checkParameterProperties: true, //是否对构造方法进行检查默认检查
  ignoreInferredTypes: false, //忽略隐式
  treatMethodsAsReadonly: false,
};
//接收参数信息
interface methodArgType {
  name?: string;
  baseType?: { name?: string };
  dimension?: number;
  genericTypes?: { classSignature?: ClassSignature; name?: string }[];
  types?: {
    classSignature?: ClassSignature;
    name?: string;
    genericTypes?: { name?: string }[];
  }[];
  classSignature?: ClassSignature;
  methodSignature?: MethodSignature;
  originalType?: { classSignature?: ClassSignature };
}
export class PreferReadonlyParametertypesCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private clsMatcher: ClassMatcher = {
    matcherType: MatcherTypes.CLASS,
  };

  private buildMatcher: MethodMatcher = {
    matcherType: MatcherTypes.METHOD,
    class: [this.clsMatcher],
  };
  public registerMatchers(): MatcherCallback[] {
    const matchBuildTs: MatcherCallback = {
      matcher: this.buildMatcher,
      callback: this.check,
    };
    return [matchBuildTs];
  }

  //此规则允许您强制函数参数解析为 readonly 类型
  public check = (targetMethod: ArkMethod) => {
    //获取Scene
    let declareClass = targetMethod.getDeclaringArkClass();
    const arkfile = declareClass.getDeclaringArkFile();
    const scene = arkfile.getScene();
    const severity = this.rule.alert ?? this.metaData.severity;
    const options: Options = this.rule.option[0] as unknown as Options;
    const mergedOptions: Options = {
      ...defaultOptions,
      ...options,
    };
    const allow = mergedOptions.allow;
    const methodPath = arkfile.getFilePath();
    let allowClass: ClassifiedItem[] = []; //存放白名单
    if (allow) {
      allowClass = this.classifyAllow(allow, methodPath);
    }

    //1.获取参数列表2.判断是否是只读
    const methodname = targetMethod.getName();
    const methodCode = targetMethod.getCode() ?? "";
    const paramsDef = this.extractParameters(methodCode);
    const jk = targetMethod.getDeclareLines();
    let lineCode =
      targetMethod.getLine() ??
      (targetMethod.getDeclareLines()
        ? targetMethod.getDeclareLines()![0] ?? 0
        : 0); //接口内或者type或者function 无行号
    let lineColCode =
      targetMethod.getColumn() ??
      (targetMethod.getDeclareColumns()
        ? targetMethod.getDeclareColumns()![0] ?? 0
        : 0);
    //checkParameterProperties  ---》true时检查------》false 不检查直接跳过
    if (
      !mergedOptions.checkParameterProperties &&
      this.checkConstructor(methodCode)
    ) {
      return;
    }

    if (targetMethod.getParameters()) {
      const params: MethodParameter[] = targetMethod.getParameters();
      for (let i = 0; i < params.length; i++) {
        const param = params[i];
        //获取参数的类别
        const paramType: methodArgType = param.getType() as methodArgType;
        const paramDef = i < paramsDef.length ? paramsDef[i] : "";
        //ignoreInferredTypes---->忽略未定义参数
        if (mergedOptions.ignoreInferredTypes && !paramDef) {
          return true;
        }
        if (
          !this.isReadonlyType(
            targetMethod,
            paramType,
            paramDef,
            scene,
            allowClass,
            mergedOptions.treatMethodsAsReadonly ?? false
          )
        ) {
          this.addIssueReport(
            [lineCode, lineColCode, methodCode],
            arkfile,
            param.getName(),
            severity
          );
        }
      }
    }
  };

  //抽取函数参数语句
  private extractParameters(methodCode: string): string[] {
    const paramPattern = /\(([^)]+)\)/;
    const match = methodCode.match(paramPattern);
    if (match && match[1] !== undefined) {
      const params = match[1];
      const paramList = [];
      let depth = 0;
      let currentParam = "";

      for (let char of params) {
        if (char === "[" || char === "{" || char === "(") {
          depth++;
        } else if (char === "]" || char === "}" || char === ")") {
          depth--;
        }
        if (char === "," && depth === 0) {
          paramList.push(currentParam.trim());
          currentParam = "";
        } else {
          currentParam += char;
        }
      }

      if (currentParam) {
        paramList.push(currentParam.trim());
      }
      return paramList;
    }
    return [];
  }

  //判断参数是否是只读
  private isReadonlyType(
    arkMethod: ArkMethod,
    argType: methodArgType,
    paramDef: string,
    scene: Scene,
    allowClass: ClassifiedItem[],
    treatMethodsAsReadonly: boolean
  ): boolean {
    const type_1 = argType.toString(); //常规类型包含数组需要过滤出数组
    const type_2 = argType.classSignature?.getClassName() ?? "-1"; //对象或者接口
    const type_3 = argType.originalType; //type
    const types = argType.types; //union 类型
    const obName = argType.name;
    const obtype = argType.genericTypes ?? []; //Object<T>
    //TypeQuery和SymbolKeyword
    //白名单---->default包无法获取
    const allows = this.findNames(allowClass);
    if (this.findNames(allowClass).includes(type_2)) {
      return true;
    }
    //type类型---->treatMethodsAsReadonly--->false
    if (type_3 && argType.originalType?.classSignature) {
      const uio = argType.originalType?.classSignature?.getClassName();
      return argType.originalType
        ? this.areAllTypePropertiesReadonly(
            argType.originalType?.classSignature,
            scene,
            allowClass,
            treatMethodsAsReadonly
          )
        : false;
    }
    //对象/接口
    if (type_2 && argType.classSignature) {
      return this.areAllClassPropertiesReadonly(
        argType.classSignature,
        scene,
        allowClass
      );
    }
    //常规类型 判断()---->fieldCode.trim().startsWith('readonly')起规范作用并非条件之一&&paramDef.trim().startsWith('readonly')
    if (this.isPrimitiveType(type_1) && paramDef) {
      return true;
    }
    const paramParts = paramDef.split(":")[1]?.trim() ?? "";
    if (
      (this.isArrayOrTupleType(type_1) || type_1 === "TypeOperator") &&
      this.isReadonlyArrayOrTuple(paramParts)
    ) {
      return true;
    }
    //处理uinon类型
    if (types && !this.isArrayOrTupleType(type_1)) {
      for (const unionType of types) {
        const unionType_1 = unionType.toString(); //常规类型包含数组需要过滤出数组
        const unionType_2 = unionType.classSignature?.getClassName(); //对象或者接口
        //对象接口
        if (
          unionType_2 &&
          unionType.classSignature &&
          !this.areAllClassPropertiesReadonly(
            unionType.classSignature,
            scene,
            allowClass
          )
        ) {
          return false;
        }
        //常规类型 判断
        if (
          this.isPrimitiveType(unionType_1) &&
          paramDef.trim().startsWith("readonly")
        ) {
          return false;
        }
        //常规数组和元组
        const paramParts = paramDef.split("|")[0]?.split(":")[1]?.trim() ?? "";
        if (
          (this.isArrayOrTupleType(unionType_1) ||
            unionType_1 === "TypeOperator") &&
          !this.isReadonlyArrayOrTuple(paramParts)
        ) {
          return false;
        }
        //其它
        const typeObName = unionType.name;
        //未定义只读类型 以上都不是并且不是tartsWith('Readonly') return false
        if (
          typeObName &&
          !unionType.classSignature &&
          !this.isArrayOrTupleType(unionType_1) &&
          unionType_1 != "TypeOperator" &&
          !this.isPrimitiveType(unionType_1) &&
          !typeObName.toString().trim().startsWith("Readonly")
        ) {
          return false;
        }
      }
      return true;
    }
    //特殊
    const otherType = ["FunctionType", "TypeQuery", "SymbolKeyword"];
    if (
      otherType.includes(type_1) ||
      argType.methodSignature ||
      type_1.startsWith("typeof")
    ) {
      return true;
    }
    //其它
    if (
      obName &&
      !argType.classSignature &&
      !otherType.includes(type_1) &&
      !this.isArrayOrTupleType(type_1) &&
      type_1 != "TypeOperator" &&
      !this.isPrimitiveType(type_1) &&
      !obName.toString().trim().startsWith("Readonly")
    ) {
      return false;
    } else {
      for (const type of obtype) {
        if (this.isArrayOrTupleType(type.toString())) {
          return false;
        }
        if (
          type.classSignature &&
          this.areAllClassPropertiesReadonly(
            type.classSignature,
            scene,
            allowClass
          )
        ) {
          return true;
        }
      }
    }

    return false;
  }

  /**
   * 判断字符串是否表示基础类型，包括 any 和 undefined
   * @param type 要检查的字符串
   * @returns 如果字符串表示基础类型，则返回 true；否则返回 false
   */
  private isPrimitiveType(type: string): boolean {
    const primitiveTypes = [
      "string",
      "number",
      "boolean",
      "symbol",
      "bigint",
      "undefined",
      "null",
      "any",
      "void",
      "never",
      "unknown",
    ];
    //unknown--->UnknownKeyword
    if (type === "UnknownKeyword") {
      return true;
    }
    return primitiveTypes.includes(type);
  }

  //检查是否是数组或者是元组
  private isArrayOrTupleType(type: string): boolean {
    // 检查是否是数组类型
    if (type.endsWith("[]")) {
      return true;
    }
    // 检查是否是元组类型
    if (type.startsWith("[") && type.endsWith("]")) {
      return true;
    }
    return false;
  }

  //判断一个数组或元组的定义字符串是否符合全部为只读
  /**
   * 判断一个数组或元组的定义字符串是否符合全部为只读
   * @param type 要检查的字符串
   * @returns 如果字符串表示的数组或元组全部为只读，则返回 true；否则返回 false
   */
  private isReadonlyArrayOrTuple(type: string): boolean {
    // 检查是否是只读数组类型
    if (type.startsWith("readonly ") && type.endsWith("[]")) {
      // 检查多维数组
      const dimensions = type.split("[]").map((dim) => dim.trim());
      dimensions.pop();
      return dimensions.every((dim) => dim.startsWith("readonly"));
    }

    // 检查是否是只读元组类型
    if (type.startsWith("readonly [") && type.endsWith("]")) {
      // 去掉开头的 'readonly [' 和结尾的 ']'
      const elements = type
        .replace(/^readonly \[/, "")
        .replace(/\]$/, "")
        .split(",");
      // 检查每个元素是否包含 'readonly'
      const op = elements.every((el) => {
        if (el.includes("[]")) {
          return this.isReadonlyArrayOrTuple(el);
        } else {
          return true;
        }
      });
      return op;
    }

    return false;
  }

  /**
   * 检查类的属性是否全部为只读
   * @param classSignature 类的签名
   * @param scene 场景对象
   * @returns 如果类的所有属性都是只读的，则返回 true；否则返回 false
   */
  areAllClassPropertiesReadonly(
    classSignature: ClassSignature,
    scene: Scene,
    allowClass: ClassifiedItem[],
    depth: number = 0,
    visitedClasses: Set<ClassSignature> = new Set() // 用于记录访问过的类
  ): boolean {
    // 防止递归调用过深
    if (depth > 100) {
      logger.warn("Recursion depth exceeded, stopping.");
      return true;
    }

    // 防止循环递归：如果该类已经访问过，直接返回
    if (visitedClasses.has(classSignature)) {
      return true;
    }

    // 标记当前类已访问
    visitedClasses.add(classSignature);

    // 白名单如果在里面返回 true
    if (this.findNames(allowClass).includes(classSignature.getClassName())) {
      return true;
    }

    const arkClass = scene.getClass(classSignature);
    const fields: ArkField[] = arkClass?.getFields() ?? [];

    for (const field of fields) {
      const fieldType = field.getType() as methodArgType;
      const type = fieldType.toString();
      const fieldCode = field.getCode();

      // 对象判断
      if (fieldType.classSignature) {
        // 递归时增加深度，并且传递 visitedClasses 来避免重复处理
        if (
          !this.areAllClassPropertiesReadonly(
            fieldType.classSignature,
            scene,
            allowClass,
            depth + 1,
            visitedClasses
          )
        ) {
          return false;
        }
      }

      // 常规类型
      if (
        this.isPrimitiveType(type) &&
        !fieldCode.trim().startsWith("readonly")
      ) {
        return false;
      }

      // 数组或元组
      const filedCodeNeed = fieldCode.split(":")[1] ?? "";
      if (
        (this.isArrayOrTupleType(type) || type === "TypeOperator") &&
        !this.isReadonlyArrayOrTuple(filedCodeNeed)
      ) {
        return false;
      }
    }

    // 处理父类
    const superClass = arkClass?.getSuperClass();
    if (superClass) {
      return this.areAllClassPropertiesReadonly(
        superClass.getSignature(),
        scene,
        allowClass,
        depth + 1,
        visitedClasses
      );
    }

    return true;
  }

  areAllTypePropertiesReadonly(
    classSignature: ClassSignature,
    scene: Scene,
    allowClass: ClassifiedItem[],
    treatMethodsAsReadonly: boolean
  ): boolean {
    const arkClass = scene.getClass(classSignature);
    if (!treatMethodsAsReadonly) {
      const methods: ArkMethod[] = arkClass?.getMethods() ?? [];
      for (const method of methods) {
        if (
          !method.getName().includes("%") &&
          !method.getCode()?.trim().startsWith("readonly")
        ) {
          return false;
        }
      }
    }
    const fields: ArkField[] = arkClass?.getFields() ?? [];
    for (const field of fields) {
      const fieldType = field.getType() as methodArgType;
      const type = fieldType.toString();
      const fieldCode = field.getCode();
      //对象判断
      if (fieldType.classSignature) {
        return this.areAllClassPropertiesReadonly(
          fieldType.classSignature,
          scene,
          allowClass
        );
      }
      //常规类型
      if (
        this.isPrimitiveType(type) &&
        !fieldCode.trim().startsWith("readonly")
      ) {
        return false;
      }
      //数组或元组
      const fieldCodeNeed = fieldCode.split(":")[1] ?? "";
      if (
        (this.isArrayOrTupleType(type) || type === "TypeOperator") &&
        !this.isReadonlyArrayOrTuple(fieldCodeNeed)
      ) {
        return false;
      }
    }
    const superClass = arkClass?.getSuperClass();
    if (superClass) {
      return this.areAllClassPropertiesReadonly(
        superClass.getSignature(),
        scene,
        allowClass
      );
    }

    return true;
  }

  //解析allow属性
  private classifyAllow(
    allow: AllowElement[],
    methodPath: string
  ): ClassifiedItem[] {
    const classifiedMap: { [key: string]: { [key: string]: Set<string> } } = {};
    allow.forEach((element) => {
      let from: string;
      let path: string;
      let names: string[];

      if (typeof element === "string") {
        from = "string";
        path = methodPath;
        names = [element];
      } else {
        from = element.from;
        if (element.from === "file") {
          path = element.path ? element.path : methodPath;
        } else {
          path =
            element.from === "package" && element.package
              ? element.package
              : "";
        }

        names = Array.isArray(element.name) ? element.name : [element.name];
      }

      if (!classifiedMap[from]) {
        classifiedMap[from] = {};
      }
      if (!classifiedMap[from][path]) {
        classifiedMap[from][path] = new Set();
      }
      names.forEach((name) => classifiedMap[from][path].add(name));
    });

    const classifiedItems: ClassifiedItem[] = [];
    for (const from in classifiedMap) {
      for (const path in classifiedMap[from]) {
        classifiedItems.push({
          from,
          path: path || undefined,
          names: Array.from(classifiedMap[from][path]),
        });
      }
    }

    return classifiedItems;
  }

  //获取所有的白名单
  private findNames(classifiedItems: ClassifiedItem[]): string[] {
    return classifiedItems.reduce((allClassNames, item) => {
      return allClassNames.concat(item.names);
    }, [] as string[]);
  }

  //是否是构造方法
  //判定是否是符合要求的构造方法名
  private checkConstructor(text: string): boolean {
    const constructorIndex = text.indexOf("constructor(");
    if (constructorIndex === -1) {
      return false;
    }
    return true;
  }

  private addIssueReport(
    linePos: [x: number, y: number, label: string],
    arkFile: ArkFile,
    name: string,
    severity: number
  ) {
    const warnInfo = this.getLineAndColumnMethod(linePos, arkFile, name);
    let defect = new Defects(
      warnInfo.line,
      warnInfo.startCol,
      warnInfo.endCol,
      this.metaData.description,
      severity,
      this.metaData.ruleId,
      warnInfo.filePath,
      this.metaData.ruleDocPath,
      true,
      false,
      false
    );
    this.issues.push(new IssueReport(defect, undefined));
    RuleListUtil.push(defect);
  }

  private getLineAndColumnMethod(
    linePos: [line: number, col: number, code: string],
    arkFile: ArkFile,
    name: string
  ) {
    const line = linePos[0];
    if (arkFile) {
      let startCol = linePos[1];
      const pos = linePos[2].indexOf(name);
      if (pos !== -1) {
        startCol += pos;
        const endCol = startCol + name.length - 1;
        const originPath = arkFile.getFilePath();
        return { line, startCol, endCol, filePath: originPath };
      }
    } else {
      logger.debug("originStmt or arkFile is null");
    }
    return { line: -1, startCol: -1, endCol: -1, filePath: "" };
  }
}
